﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace MetaSharp.Transformation
{
	public class Match : IMatch
	{
        public readonly static PatternMatch Fail = s => Task.FromResult<IMatch>(new Match(false, false, null, s, s, Map<string, object>.Empty, new ArrayList<Error>()));
        public readonly static PatternMatch Default = s => Task.FromResult<IMatch>(new Match(true, false, null, s, s, Map<string, object>.Empty, new ArrayList<Error>()));
        public readonly static Func<IScope, IMatch> LR = s => new Match(false, true, null, s, s, Map<string, object>.Empty, new ArrayList<Error>());

		public IImmutableArray<Error> Errors { get; private set; }
        public IScope Start { get; private set; }
		public IScope End { get; private set; }
		public object Value { get; private set; }
		public bool Matched { get; private set; }
		public bool IsLR { get; private set; }
		public Map<string, object> Variables { get; private set; }

		private Match()
		{
		}

		private Match(
			bool matched,
			bool isLR,
			object value,
            IScope start,
			IScope end,
			Map<string, object> variables,
            IImmutableArray<Error> errors)
		{
			this.Matched = matched;
			this.IsLR = isLR;
			this.Value = value;
            this.Start = start;
			this.End = end;
			this.Variables = variables;
			this.Errors = errors;
		}

		public Match(object value, IScope start, IScope end, params Error[] errors)
			: this(value, start, end, new ArrayList<Error>(errors))
		{
		}

        public Match(object value, IScope start, IScope end, IImmutableArray<Error> errors)
            : this(true, false, value, start, end, Map<string, object>.Empty, errors)
        {
        }

        public IMatch AddErrors(params Error[] errors)
        {
            return new Match(
                this.Matched,
                this.IsLR,
                this.Value,
                this.Start,
                this.End,
                this.Variables,
                this.Errors.Concat(new ArrayList<Error>(errors)));
        }


        public IMatch AddErrors(IImmutableArray<Error> errors)
		{
			return new Match(
                this.Matched,
                this.IsLR,
				this.Value,
                this.Start,
                this.End,
                this.Variables,
				this.Errors.Concat(errors));
		}

		public IMatch AddVariable(string name, object value)
		{
			return new Match(
                this.Matched,
                this.IsLR,
				this.Value,
                this.Start,
                this.End.AddVariable(name, value),
                this.Variables,
				this.Errors);
		}

		public IMatch AddVariables(Map<string, object> variables)
		{
			return new Match(
                this.Matched,
                this.IsLR,
				this.Value,
                this.Start,
                this.End.AddVariables(variables),
                this.Variables,
				this.Errors);
		}

		public IMatch SetEnd(IScope end)
		{
			return new Match(
				this.Matched,
				this.IsLR,
				this.Value,
                this.Start,
                end,
				this.Variables,
				this.Errors);
		}

		public IMatch SetValue(object value)
		{
			return new Match(
                this.Matched,
                this.IsLR,
				value,
                this.Start,
                this.End,
                this.Variables,
				this.Errors);
		}

        public IMatch PopRule()
        {
            var end = this.End.PopRule(this);
            return new Match(
                this.Matched,
                this.IsLR,
                this.Value,
                this.Start,
                end,
                this.Variables,
                this.Errors);
        }

        public IScope Step()
        {
            var memos = this.End.Memos.Step();

            var values = this.Value as IEnumerable;
            if (values == null)
            {
                values = new[] { this.Value };
            }

            var scope = new Scope(
                new Stream(values),
                new ImmutableStack<RuleEntry>(),
                Map<string, object>.Empty,
                memos);

            return scope;
        }

        public SourceSpan Trace()
        {
            return this.End.Memos.Trace(this.Value);
        }
	}
}
